Creating a Basic Interactive Dashboard with hvPlot and Panel

Overview

Interactive visualuzation is …

By the end of this notebook, you should be able to:

  • Understand the necessecity for interactive plots and the challenges associated with them

  • Use hvPlot to generate basic interactive plots with Xarray

  • Create an interactive dashboard for visualuzing geoscience datasets using Panel and hvPlot

Prerequisites

Concepts

Importance

Notes

Intro to Xarray

Necessary

  • Time to learn: 30 minutes

Imports

import xarray as xr

import panel as pn

import holoviews as hv
from holoviews import opts

hv.extension("bokeh")

Data

As was discussed in the Data Aquisition …

rda_url = 'https://data.rda.ucar.edu/'
annual_means = rda_url + 'pythia_era5_24/annual_means/'
xrds = xr.open_dataset(annual_means + "temp_2m_annual_1940_2023.zarr", engine= 'zarr')
xrds.load()

xrds['VAR_2T_ANOM_FROM_1940'] = xrds['VAR_2T'] - xrds['VAR_2T'][0]
xrds
<xarray.Dataset> Size: 698MB
Dimensions:                (time: 84, latitude: 721, longitude: 1440)
Coordinates:
  * latitude               (latitude) float64 6kB 90.0 89.75 ... -89.75 -90.0
  * longitude              (longitude) float64 12kB 0.0 0.25 0.5 ... 359.5 359.8
  * time                   (time) datetime64[ns] 672B 1940-12-31 ... 2023-12-31
Data variables:
    VAR_2T                 (time, latitude, longitude) float32 349MB 258.2 .....
    VAR_2T_ANOM_FROM_1940  (time, latitude, longitude) float32 349MB 0.0 ... ...

Considerations for Interactive Plots

Add some markdown text on some of the following ideas:

  • What are some reasons we want to make data visualuzation interactive?

Baisc Interactivity using hvPlot

The hvPlot package is a familiar and high level API for data exploration and visualuzation.

One of the most powerfull features of hvPlot is that it provides an alternative plotting API that directly attaches to existing Python objects through the .hvplot() attribute. For the case of Xarray, importing hvplot.xarray adds a brand new set of plotting routines accessible either through xr.DataArray.hvplot() or xr.Dataset.hvplot()

import hvplot.xarray

Before using hvPlot, let’s take a look at the default Xarray plotting methods.

xrds['VAR_2T'].plot()
(array([ 1038607.,  3420082.,  1676489.,  2612838.,  9834305.,  9283152.,
        12940102., 12676054., 22733444., 10997087.]),
 array([215.00773621, 224.33287048, 233.65800476, 242.98313904,
        252.30827332, 261.63339233, 270.95852661, 280.28366089,
        289.60879517, 298.93392944, 308.25906372]),
 <BarContainer object of 10 artists>)
../_images/bf869cd9e6f73014fe58e8d2952fe6d765f26dee2179ea0a026de8771cd18505.png

We can replace the .plot() function call with .hvplot(). By default, hvPlot uses the Bokeh backend, which has naitive interactive tools, such as :

  • Panning

  • Box Select

  • Scroll Zoom

  • Saving

  • Resetting

xrds['VAR_2T'].hvplot()

If we wanted to plot …

xrds['VAR_2T'].isel(time=0).plot()
<matplotlib.collections.QuadMesh at 0x7f3010553a90>
../_images/489867cb5303a9fe50a659124e4bcd4860580d69907bc1f9f2a7c08aef8bcd5d.png

Switching

xrds['VAR_2T'].isel(time=0).hvplot()

Time Widget

Climate data typically comes with multiple timesteps. We can create a basic widget that allows us to seek through time by setting the groupby='time' parameter in our .hvplot() call.

xrds['VAR_2T'].hvplot(groupby='time', widget_location="bottom")

You may notice that our colorbar is dynamically changing as we change our time steps. We can fix the colorbar by setting a clim value, which is a tuple of the minimum and maximum desired colorbar range.

One suggestion is to use the minimum and maximum of the data variable you are visualuzing across time.

clim = (xrds['VAR_2T'].values.min(), xrds['VAR_2T'].values.max())
xrds['VAR_2T'].hvplot(clim=clim, groupby='time', widget_location="bottom")

You may have noticed that there is a slight lag when switching time steps. This is due to hvPlot plotting the full resolution of our dataset. We can instead rasterize the output by setting rasterize=True, which will significantly improve the perfromance of our interactive plot.

xrds['VAR_2T'].hvplot(rasterize=True, clim=clim, groupby='time', widget_location="bottom")

Animation Widget

Another usefull interactive feature is animations. Instead of manually scrolling through time, we can set up a widget that lets us animate our data across time. This can be achieved by adding a Scrubber widget to our plot by setting widget_type="scrubber"

xrds['VAR_2T'].hvplot(
    rasterize=True,
    groupby="time",
    widget_type="scrubber",
    widget_location="bottom",
)

Creating a Dashboard

Dataset Widgets

w_time = pn.widgets.IntSlider(name='Year', start=0, end=83)
w_var = pn.widgets.Select(name='Data Variable', options=list(xrds.data_vars))

dataset_controls = pn.WidgetBox(
                                '## Dataset Controls', 
                                w_var, 
                                )
dataset_controls

Plotting Widgets

w_cmap = pn.widgets.Select(name='Colormap', options=['inferno', 'plasma', 'coolwarm'])


w_plot_type = pn.widgets.Select(name='Plot Type', options=['Color Plot', 'Contour', 'Filled Contour'])


plot_controls = pn.WidgetBox(
                            '## Plot Controls',
                            w_plot_type,
                            w_cmap, 
                            )
plot_controls

Animation Widgets

w_player = pn.widgets.Player(
    value=0,
    start=0,
    end=83,
    name="Year",
    loop_policy="loop",
    interval=300,
    align="center",
    width_policy='fit'
)
w_player

Plotting Function

def plot_ds(time, var, cmap, plot_type):
    clim = (xrds[var].values.min(), xrds[var].values.max())
    
    if plot_type == "Color Plot":
        return xrds[var].isel(time=time).hvplot(cmap=cmap, 
                                                              title=str(f"{var} year {time}"),
                                                              clim=clim,
                                                              dynamic=False,
                                                              rasterize=True,
                                                              precompute=True,
                                               ).opts(framewise=False)
    
    elif plot_type == "Contour":
        return xrds[var].isel(time=time).hvplot.contour(cmap=cmap,
                                          dynamic=False,
                                          rasterize=True,
                                          title=str(f"{var} Year: {time}"),
                                          clim=clim,
                                          precompute=True,).opts(framewise=False)
    elif plot_type == "Filled Contour":
        return xrds[var].isel(time=time).hvplot.contourf(cmap=cmap,
                                          dynamic=False,
                                          rasterize=True,
                                          title=str(f"{var} Year: {time}"),
                                          clim=(200, 300),
                                          precompute=True,).opts(framewise=False)                 

Putting it all Together

controls = pn.Column(dataset_controls, plot_controls)

app = pn.Row(
    controls,
    pn.Column(pn.panel(
        hv.DynamicMap(pn.bind(
            plot_ds, 
            time=w_player,
            var=w_var,
            cmap=w_cmap,
            plot_type = w_plot_type
        )
                     )
    ),
             w_player)
)

app